//
// Created by ct on 2020/10/14.
//

#include <iostream>
#include "request_parser.h"

#include "request.h"

namespace http {
    namespace server {
        request_parser::request_parser() : state_(method_start) {}

        void request_parser::reset() {
            state_ = method_start;
        }

        request_parser::result_type request_parser::consume(request &req, char input) {
            switch (state_) {
                case method_start:
                    if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
                        return bad;
                    } else {
                        state_ = method;
                        req.method.push_back(input);
                        return indeterminate;
                    }
                case method:
                    if (input == ' ') {
                        state_ = uri;
                        return indeterminate;
                    } else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
                        return bad;
                    } else {
                        req.method.push_back(input);
                        return indeterminate;
                    }
                case uri:
                    if (input == ' ')
                    {
                        state_ = http_version_h;
                        return indeterminate;
                    }
                    else if (is_ctl(input))
                    {
                        return bad;
                    }
                    else
                    {
                        req.uri.push_back(input);
                        return indeterminate;
                    }
                case http_version_h:
                    if (input == 'H')
                    {
                        state_ = http_version_t_1;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case http_version_t_1:
                    if (input == 'T')
                    {
                        state_ = http_version_t_2;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case http_version_t_2:
                    if (input == 'T')
                    {
                        state_ = http_version_p;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case http_version_p:
                    if (input == 'P')
                    {
                        state_ = http_version_slash;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case http_version_slash:
                    if (input == '/')
                    {
                        req.http_version_major = 0;
                        req.http_version_minor = 0;
                        state_ = http_version_major_start;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case http_version_major_start:
                    if (is_digit(input))
                    {
                        req.http_version_major = req.http_version_major * 10 + input - '0';
                        state_ = http_version_major;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case http_version_major:
                    if (input == '.')
                    {
                        state_ = http_version_minor_start;
                        return indeterminate;
                    }
                    else if (is_digit(input))
                    {
                        req.http_version_major = req.http_version_major * 10 + input - '0';
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case http_version_minor_start:
                    if (is_digit(input))
                    {
                        req.http_version_minor = req.http_version_minor * 10 + input - '0';
                        state_ = http_version_minor;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case http_version_minor:
                    if (input == '\r')
                    {
                        state_ = expecting_newline_1;
                        return indeterminate;
                    }
                    else if (is_digit(input))
                    {
                        req.http_version_minor = req.http_version_minor * 10 + input - '0';
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case expecting_newline_1:
                    if (input == '\n')
                    {
                        state_ = header_line_start;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case header_line_start:
                    if (input == '\r')
                    {
                        state_ = expecting_newline_3;
                        return indeterminate;
                    }
                    else if (!req.headers.empty() && (input == ' ' || input == '\t'))
                    {
                        state_ = header_lws;
                        return indeterminate;
                    }
                    else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
                    {
                        return bad;
                    }
                    else
                    {
                        req.headers.push_back(header());
                        req.headers.back().name.push_back(input);
                        state_ = header_name;
                        return indeterminate;
                    }
                case header_lws:
                    if (input == '\r')
                    {
                        state_ = expecting_newline_2;
                        return indeterminate;
                    }
                    else if (input == ' ' || input == '\t')
                    {
                        return indeterminate;
                    }
                    else if (is_ctl(input))
                    {
                        return bad;
                    }
                    else
                    {
                        state_ = header_value;
                        req.headers.back().value.push_back(input);
                        return indeterminate;
                    }
                case header_name:
                    if (input == ':')
                    {
                        state_ = space_before_header_value;
                        return indeterminate;
                    }
                    else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
                    {
                        return bad;
                    }
                    else
                    {
                        req.headers.back().name.push_back(input);
                        return indeterminate;
                    }
                case space_before_header_value:
                    if (input == ' ')
                    {
                        state_ = header_value;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case header_value:
                    if (input == '\r')
                    {
                        state_ = expecting_newline_2;
                        return indeterminate;
                    }
                    else if (is_ctl(input))
                    {
                        return bad;
                    }
                    else
                    {
                        req.headers.back().value.push_back(input);
                        return indeterminate;
                    }
                case expecting_newline_2:
                    if (input == '\n')
                    {
                        state_ = header_line_start;
                        return indeterminate;
                    }
                    else
                    {
                        return bad;
                    }
                case expecting_newline_3:
                    return (input == '\n') ? good : bad;
                default:
                    return bad;
            }

        }

        bool request_parser::is_char(int c)
        {
            return c >= 0 && c <= 127;
        }

        bool request_parser::is_ctl(int c)
        {
            return (c >= 0 && c <= 31) || (c == 127);
        }

        bool request_parser::is_tspecial(int c)
        {
            switch (c)
            {
                case '(': case ')': case '<': case '>': case '@':
                case ',': case ';': case ':': case '\\': case '"':
                case '/': case '[': case ']': case '?': case '=':
                case '{': case '}': case ' ': case '\t':
                    return true;
                default:
                    return false;
            }
        }

        bool request_parser::is_digit(int c)
        {
            return c >= '0' && c <= '9';
        }

    } // namespace server
} // namespace http